/*
 * Copyright (c) 2006-2020, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-05-21     chenhy       the first version
 */
#include "lfs_if.h"
#include "board.h"
#include "dev_api.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define LFS_CACHE_SIZE      256

lfs_t lfs_flash;
lfs_file_t lfs_file;
lfs_dir_t lfs_dir;
struct lfs_config lfs_cfg_flash;
uint8_t read_buf[LFS_CACHE_SIZE];
uint8_t prog_buf[LFS_CACHE_SIZE];
uint8_t file_cache[LFS_CACHE_SIZE];
uint8_t lookahead_buf[2];

int lfs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
  uint32_t read_addr;
  read_addr = c->block_size * block + off + USER_DATA_FIELD_START;
  flash_read(read_addr, (uint8_t*)buffer, size);
  return 0;
}

int lfs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
  uint32_t prog_addr;
  prog_addr = c->block_size * block + off + USER_DATA_FIELD_START;
  flash_write(prog_addr, (uint8_t*)buffer, size);
  return 0;
}

int lfs_erase(const struct lfs_config *c, lfs_block_t block)
{
  uint32_t erase_addr;
  erase_addr = c->block_size * block + USER_DATA_FIELD_START;
  flash_erase(erase_addr);
  return 0;
}

int lfs_sync(const struct lfs_config *c)
{
  return 0;
}

struct lfs_config cfg = {
  // block device operations
  .read  = lfs_read,
  .prog  = lfs_prog,
  .erase = lfs_erase,
  .sync  = lfs_sync,

  // block device configuration
  .read_size = LFS_CACHE_SIZE,
  .prog_size = LFS_CACHE_SIZE,
  .block_size = 2048,
  .block_count = 54,
  .cache_size = LFS_CACHE_SIZE,
  .lookahead_size = 2,
  .block_cycles = -1,
  .read_buffer = (void*)read_buf,
  .prog_buffer = (void*)prog_buf,
  .lookahead_buffer = (void*)lookahead_buf,
};

struct lfs_file_config file_cfg = {
  .buffer = (void*)file_cache,
  .attrs = NULL,
  .attr_count = 0
};

void *lfs_malloc(size_t size)
{
  return NULL;
}

void lfs_free(void *p)
{
  ;
}

lfs_file_t* file_open(const char *path, int flags)
{
  int err;
  err = lfs_file_opencfg(&lfs_flash, &lfs_file, path, flags, &file_cfg);
  if(err != LFS_ERR_OK) return NULL;
  return &lfs_file;
}

int file_write(void *pbuf, int size, lfs_file_t *file)
{
  return lfs_file_write(&lfs_flash, file, pbuf, size);
}

int file_read(void *pbuf, int size, lfs_file_t *file)
{
  return lfs_file_read(&lfs_flash, file, pbuf, size);
}

int file_close(lfs_file_t *file)
{
  return lfs_file_close(&lfs_flash, file);
}

int file_seek(lfs_file_t *file, int offset, int whence)
{
  return lfs_file_seek(&lfs_flash, file, offset, whence);
}

int file_tell(lfs_file_t *file)
{
  return lfs_file_tell(&lfs_flash, file);
}

void file_rewind(lfs_file_t *file)
{
  lfs_file_rewind(&lfs_flash, file);
}

int file_truncate(int size, lfs_file_t *file)
{
  return lfs_file_truncate(&lfs_flash, file, size);
}

int file_remove(const char *path)
{
  return lfs_remove(&lfs_flash, path);
}

int file_rename(const char *oldpath, const char *newpath)
{
  return lfs_rename(&lfs_flash, oldpath, newpath);
}

int file_getc(lfs_file_t *file)
{
  char data;
  if(file_read(&data, 1, file) == 1) return (int)data;
  else return -1;
}

char* file_gets(char *str, int n, lfs_file_t *file)
{
  int i;
  for(i=0;i<n;i++) {
    if(file_read(&str[i], 1, file) < 0) return NULL;
    if(str[i] == '\n') break;
  }
  return str;
}

int file_putc(int data, lfs_file_t *file)
{
  if(file_write(&data, 1, file) == 1) return (int)data;
  else return -1;
}

int file_puts(char *str, lfs_file_t *file)
{
  int length;
  length = strlen(str);
  return file_write(str, length, file);
}

lfs_dir_t* dir_open(const char *path)
{
  int ret;
  ret = lfs_dir_open(&lfs_flash, &lfs_dir, path);
  if(ret != LFS_ERR_OK) return NULL;
  return &lfs_dir;
}

int dir_close(lfs_dir_t *dir)
{
  return lfs_dir_close(&lfs_flash, dir);
}

int dir_read(lfs_dir_t *dir, struct lfs_info *info)
{
  return lfs_dir_read(&lfs_flash, dir, info);
}

int lfs_startup(void)
{
  // mount the filesystem
  // reformat if we can't mount the filesystem
  // this should only happen on the first boot
  if(lfs_mount(&lfs_flash, &cfg) < 0) {
    printf("format flash\n");
    lfs_format(&lfs_flash, &cfg);
    if(lfs_mount(&lfs_flash, &cfg) < 0) return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}

static int _traverse_df_cb(void *p, lfs_block_t block)
{
	int *nb = p;
	*nb += 1;
	return 0;
}

int lfs_df(void)
{
	int err;
  int available;
	int _df_nballocatedblock = 0;
	err = lfs_fs_traverse(&lfs_flash, _traverse_df_cb, &_df_nballocatedblock);
	if(err < 0){
		return err;
	}
	available = cfg.block_count*2048 - _df_nballocatedblock*2048;
	return available;
}

